package org.zn.note.jdk.java8.Java8InAction;

import org.zn.note.jdk.java8.Java8InAction.bean.Transaction;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 第6章 用流收集数据
 * <p>
 * {@link Stream#collect(Collector)} 终端操作，通过对Stream中每个元素执行Collector，最终合并成一个结果。
 * {@link Collectors} 里面有很多静态方法，可以返回Collector，具体例子 {@link #testCollectors()}
 * <p>
 * {@link Stream#reduce(BinaryOperator)} 终端操作，对Stream<T>依次执行：
 * (t1, t2) --> {操作, return T实例}， (上次返回的T实例, t3) --> {操作, return T实例}， ... 以此类推
 * 最终，Stream<T>中每个元素依次“计算”后，得到一个结果，一个T实例 （Optional<T>）
 * <p>
 * 关于 stream().collect(Collectors.reducing()) 和 stream().reduce
 * 能实现相同的效果，但是：
 * 1、语义问题：
 * stream().reduce 是把两个值结合后生成新值！是不可变归约！
 * reducing 是要改变值，最终起到在这个值上累计的效果。
 * 所以下面例子里，改变了List里的值，所以要用reducing
 * 2、实际问题：并行！后面再说
 * <p>
 * 归约操作工作原理总结：把一个初始化为起始值的累加器，和把转换函数应用到流中每个元素上得到的结果，不断迭代合并切来。
 * <p>
 * 为什么要引入 Collectors框架：更高层次的抽象！（就是用着方便，看着易懂）
 */
public class Ch6 {

    private static final List<Transaction> transactions = Transaction.getTransactioins(10);

    /**
     * 收集器工具类的各种方法
     * {@link Collectors}
     */
    private static void testCollectors() {

        // 最大值
        Comparator<Transaction> amountTransComparator = Comparator.comparingLong(Transaction::getAmount);
        Optional<Transaction> max =
                transactions.stream()
                        .collect(Collectors.maxBy(amountTransComparator));
        System.out.printf("Collectors.maxBy:\n%s\n\n", max.get());

        // 统计数据
        LongSummaryStatistics summary = // Statistics包含count、sum、max、min、average
                transactions.stream().collect(
                        Collectors.summarizingLong(Transaction::getAmount));
        System.out.printf("Collectors.summarizingLong:\n%s\n\n", summary);

        // 拼接字符串
        // 调用每个CharSequence对象toString，拼接成一个String （内部用的StringBuilder）
        String s1 = transactions.stream()
                .map(Transaction::getTransType) // joining需要CharSequence对象流
                .collect(Collectors.joining());
        String s2 = transactions.stream().map(Transaction::getTransType)
                .collect(Collectors.joining(", ")); // 带分隔符的
        String s3 = transactions.stream().map(Transaction::getTransType)
                .collect(Collectors.joining(", ", "[", "]")); // 带前缀、后缀的
        System.out.printf("Collectors.joining:\ns1:%s\ns2:%s\ns3:%s\n\n", s1, s2, s3);

        // 自定义功能
        // Stream<T>中元素依次计算后，得到一个T实例
        Optional<Transaction> result = transactions.stream().reduce(
                (a, b) -> {
                    a.setAmount(a.getAmount() + b.getAmount());
                    return a;
                }
        );
        System.out.printf("Stream.reduce1:\n%s\n\n", result.get());
        // 上例带初始值的
        Transaction t = transactions.stream().reduce(
                new Transaction(9999L, null, null, 0L),
                (a, b) -> {
                    a.setAmount(a.getAmount() + b.getAmount());
                    return a;
                }
        );
        System.out.printf("Stream.reduce2:\n%s\n\n", t);
        // 另外一种
        result = transactions.stream().collect(Collectors.reducing(
                (a, b) -> {
                    a.setAmount(a.getAmount() + b.getAmount());
                    return a;
                }
        ));
        System.out.printf("Stream.reduce3:\n%s\n\n", result.get());

        // 分组：按交易币种分组
        Map<Currency, List<Transaction>> t1 = null;
//                transactions.stream().collect(
//                        Collectors.groupingBy(Transaction::getCurrency)
//                );
        System.out.printf("Collectors.groupingBy:\n%s\n\n", t1);

        // 多级分组：先按交易币种分组，在每个组内再按交易类型分组
        Map<Currency, Map<String, List<Transaction>>> t2 = null;
//                transactions.stream().collect(
//                        Collectors.groupingBy(
//                                Transaction::getCurrency,
//                                Collectors.groupingBy(Transaction::getTransType)
//                        )
//                );
        System.out.printf("Collectors.groupingBy:\n%s\n\n", t2);
    }

    public static void main(String[] args) {
        testCollectors();
    }

    // 取[0, 3]整数随机数
    private static void randomInt0To3() {
        System.out.println("---------- randomInt0To3 ------------");
        Random r = new Random();
        // 想试一下，取 1~4 之间整数的随机数
        // 老的办法，for
//        for(int i=0; i<100; i++)
//            System.out.println(r.nextInt(4));
        // 新的办法，stream
        Stream.generate(() -> r.nextInt(4)).limit(100).forEach(System.out::println);
    }
}
